package org.apache.archiva.metadata.repository.jcr; /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ import org.apache.archiva.metadata.model.ArtifactMetadata; import org.apache.archiva.metadata.model.CiManagement; import org.apache.archiva.metadata.model.Dependency; import org.apache.archiva.metadata.model.IssueManagement; import org.apache.archiva.metadata.model.License; import org.apache.archiva.metadata.model.MailingList; import org.apache.archiva.metadata.model.MetadataFacet; import org.apache.archiva.metadata.model.MetadataFacetFactory; import org.apache.archiva.metadata.model.Organization; import org.apache.archiva.metadata.model.ProjectMetadata; import org.apache.archiva.metadata.model.ProjectVersionMetadata; import org.apache.archiva.metadata.model.ProjectVersionReference; import org.apache.archiva.metadata.model.Scm; import org.apache.archiva.metadata.repository.MetadataRepository; import org.apache.archiva.metadata.repository.MetadataRepositoryException; import org.apache.archiva.metadata.repository.MetadataResolutionException; import org.apache.commons.lang.StringUtils; import org.apache.jackrabbit.commons.JcrUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.collect.ImmutableMap; import javax.jcr.InvalidItemStateException; import javax.jcr.NamespaceRegistry; import javax.jcr.Node; import javax.jcr.NodeIterator; import javax.jcr.PathNotFoundException; import javax.jcr.Property; import javax.jcr.Repository; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.SimpleCredentials; import javax.jcr.ValueFactory; import javax.jcr.Workspace; import javax.jcr.nodetype.NodeTypeManager; import javax.jcr.nodetype.NodeTypeTemplate; import javax.jcr.query.Query; import javax.jcr.query.QueryResult; import javax.jcr.query.Row; import javax.jcr.query.RowIterator; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; /** * TODO below: revise storage format for project version metadata * TODO revise reference storage */ public class JcrMetadataRepository implements MetadataRepository { private static final String JCR_LAST_MODIFIED = "jcr:lastModified"; static final String NAMESPACE_NODE_TYPE = "archiva:namespace"; static final String PROJECT_NODE_TYPE = "archiva:project"; static final String PROJECT_VERSION_NODE_TYPE = "archiva:projectVersion"; static final String ARTIFACT_NODE_TYPE = "archiva:artifact"; static final String FACET_NODE_TYPE = "archiva:facet"; private static final String DEPENDENCY_NODE_TYPE = "archiva:dependency"; private final Map<String, MetadataFacetFactory> metadataFacetFactories; private Logger log = LoggerFactory.getLogger( JcrMetadataRepository.class ); private Repository repository; private Session jcrSession; public JcrMetadataRepository( Map<String, MetadataFacetFactory> metadataFacetFactories, Repository repository ) throws RepositoryException { this.metadataFacetFactories = metadataFacetFactories; this.repository = repository; } static void initialize( Session session ) throws RepositoryException { // TODO: consider using namespaces for facets instead of the current approach: // (if used, check if actually called by normal injection) // for ( String facetId : metadataFacetFactories.keySet() ) // { // session.getWorkspace().getNamespaceRegistry().registerNamespace( facetId, facetId ); // } Workspace workspace = session.getWorkspace(); NamespaceRegistry registry = workspace.getNamespaceRegistry(); if ( !Arrays.asList( registry.getPrefixes() ).contains( "archiva" ) ) { registry.registerNamespace( "archiva", "http://archiva.apache.org/jcr/" ); } NodeTypeManager nodeTypeManager = workspace.getNodeTypeManager(); registerMixinNodeType( nodeTypeManager, JcrMetadataRepository.NAMESPACE_NODE_TYPE ); registerMixinNodeType( nodeTypeManager, JcrMetadataRepository.PROJECT_NODE_TYPE ); registerMixinNodeType( nodeTypeManager, JcrMetadataRepository.PROJECT_VERSION_NODE_TYPE ); registerMixinNodeType( nodeTypeManager, JcrMetadataRepository.ARTIFACT_NODE_TYPE ); registerMixinNodeType( nodeTypeManager, JcrMetadataRepository.FACET_NODE_TYPE ); registerMixinNodeType( nodeTypeManager, JcrMetadataRepository.DEPENDENCY_NODE_TYPE ); } private static void registerMixinNodeType( NodeTypeManager nodeTypeManager, String name ) throws RepositoryException { NodeTypeTemplate nodeType = nodeTypeManager.createNodeTypeTemplate(); nodeType.setMixin( true ); nodeType.setName( name ); // for now just don't re-create - but in future if we change the definition, make sure to remove first as an // upgrade path if ( !nodeTypeManager.hasNodeType( name ) ) { nodeTypeManager.registerNodeType( nodeType, false ); } } @Override public void updateProject( String repositoryId, ProjectMetadata project ) throws MetadataRepositoryException { updateProject( repositoryId, project.getNamespace(), project.getId() ); } private void updateProject( String repositoryId, String namespace, String projectId ) throws MetadataRepositoryException { updateNamespace( repositoryId, namespace ); try { getOrAddProjectNode( repositoryId, namespace, projectId ); } catch ( RepositoryException e ) { throw new MetadataRepositoryException( e.getMessage(), e ); } } @Override public void updateArtifact( String repositoryId, String namespace, String projectId, String projectVersion, ArtifactMetadata artifactMeta ) throws MetadataRepositoryException { updateNamespace( repositoryId, namespace ); try { Node node = getOrAddArtifactNode( repositoryId, namespace, projectId, projectVersion, artifactMeta.getId() ); Calendar cal = Calendar.getInstance(); cal.setTime( artifactMeta.getFileLastModified() ); node.setProperty( JCR_LAST_MODIFIED, cal ); cal = Calendar.getInstance(); cal.setTime( artifactMeta.getWhenGathered() ); node.setProperty( "whenGathered", cal ); node.setProperty( "size", artifactMeta.getSize() ); node.setProperty( "md5", artifactMeta.getMd5() ); node.setProperty( "sha1", artifactMeta.getSha1() ); node.setProperty( "version", artifactMeta.getVersion() ); // iterate over available facets to update/add/remove from the artifactMetadata for ( String facetId : metadataFacetFactories.keySet() ) { MetadataFacet metadataFacet = artifactMeta.getFacet( facetId ); if ( metadataFacet == null ) { continue; } if ( node.hasNode( facetId ) ) { node.getNode( facetId ).remove(); } if ( metadataFacet != null ) { // recreate, to ensure properties are removed Node n = node.addNode( facetId ); n.addMixin( FACET_NODE_TYPE ); for ( Map.Entry<String, String> entry : metadataFacet.toProperties().entrySet() ) { n.setProperty( entry.getKey(), entry.getValue() ); } } } } catch ( RepositoryException e ) { throw new MetadataRepositoryException( e.getMessage(), e ); } } @Override public void updateProjectVersion( String repositoryId, String namespace, String projectId, ProjectVersionMetadata versionMetadata ) throws MetadataRepositoryException { updateProject( repositoryId, namespace, projectId ); try { Node versionNode = getOrAddProjectVersionNode( repositoryId, namespace, projectId, versionMetadata.getId() ); versionNode.setProperty( "name", versionMetadata.getName() ); versionNode.setProperty( "description", versionMetadata.getDescription() ); versionNode.setProperty( "url", versionMetadata.getUrl() ); versionNode.setProperty( "incomplete", versionMetadata.isIncomplete() ); // FIXME: decide how to treat these in the content repo if ( versionMetadata.getScm() != null ) { versionNode.setProperty( "scm.connection", versionMetadata.getScm().getConnection() ); versionNode.setProperty( "scm.developerConnection", versionMetadata.getScm().getDeveloperConnection() ); versionNode.setProperty( "scm.url", versionMetadata.getScm().getUrl() ); } if ( versionMetadata.getCiManagement() != null ) { versionNode.setProperty( "ci.system", versionMetadata.getCiManagement().getSystem() ); versionNode.setProperty( "ci.url", versionMetadata.getCiManagement().getUrl() ); } if ( versionMetadata.getIssueManagement() != null ) { versionNode.setProperty( "issue.system", versionMetadata.getIssueManagement().getSystem() ); versionNode.setProperty( "issue.url", versionMetadata.getIssueManagement().getUrl() ); } if ( versionMetadata.getOrganization() != null ) { versionNode.setProperty( "org.name", versionMetadata.getOrganization().getName() ); versionNode.setProperty( "org.url", versionMetadata.getOrganization().getUrl() ); } int i = 0; for ( License license : versionMetadata.getLicenses() ) { versionNode.setProperty( "license." + i + ".name", license.getName() ); versionNode.setProperty( "license." + i + ".url", license.getUrl() ); i++; } i = 0; for ( MailingList mailingList : versionMetadata.getMailingLists() ) { versionNode.setProperty( "mailingList." + i + ".archive", mailingList.getMainArchiveUrl() ); versionNode.setProperty( "mailingList." + i + ".name", mailingList.getName() ); versionNode.setProperty( "mailingList." + i + ".post", mailingList.getPostAddress() ); versionNode.setProperty( "mailingList." + i + ".unsubscribe", mailingList.getUnsubscribeAddress() ); versionNode.setProperty( "mailingList." + i + ".subscribe", mailingList.getSubscribeAddress() ); versionNode.setProperty( "mailingList." + i + ".otherArchives", join( mailingList.getOtherArchives() ) ); i++; } if ( !versionMetadata.getDependencies().isEmpty() ) { Node dependenciesNode = JcrUtils.getOrAddNode( versionNode, "dependencies" ); for ( Dependency dependency : versionMetadata.getDependencies() ) { // Note that we deliberately don't alter the namespace path - not enough dependencies for // number of nodes at a given depth to be an issue. Similarly, we don't add subnodes for each // component of the ID as that creates extra depth and causes a great cost in space and memory // FIXME: change group ID to namespace // FIXME: change to artifact's ID - this is constructed by the Maven 2 format for now. // This won't support types where the extension doesn't match the type. // (see also Maven2RepositoryStorage#readProjectVersionMetadata construction of POM) String id = dependency.getGroupId() + ";" + dependency.getArtifactId() + "-" + dependency.getVersion(); if ( dependency.getClassifier() != null ) { id += "-" + dependency.getClassifier(); } id += "." + dependency.getType(); Node n = JcrUtils.getOrAddNode( dependenciesNode, id ); n.addMixin( DEPENDENCY_NODE_TYPE ); // FIXME: remove temp code just to make it keep working n.setProperty( "groupId", dependency.getGroupId() ); n.setProperty( "artifactId", dependency.getArtifactId() ); n.setProperty( "version", dependency.getVersion() ); n.setProperty( "type", dependency.getType() ); n.setProperty( "classifier", dependency.getClassifier() ); n.setProperty( "scope", dependency.getScope() ); n.setProperty( "systemPath", dependency.getSystemPath() ); n.setProperty( "optional", dependency.isOptional() ); // node has no native content at this time, just facets // no need to list a type as it's implied by the path. Parents are Maven specific. // FIXME: add scope, systemPath, type, version, classifier & maven2 specific IDs as a facet // (should also have been added to the Dependency) // TODO: add a property that is a weak reference to the originating artifact, creating it if // necessary (without adding the archiva:artifact mixin so that it doesn't get listed as an // artifact, which gives a different meaning to "incomplete" which is a known local project // that doesn't have metadata yet but has artifacts). (Though we may want to give it the // artifact mixin and another property to identify all non-local artifacts for the closure // reports) } } for ( MetadataFacet facet : versionMetadata.getFacetList() ) { // recreate, to ensure properties are removed if ( versionNode.hasNode( facet.getFacetId() ) ) { versionNode.getNode( facet.getFacetId() ).remove(); } Node n = versionNode.addNode( facet.getFacetId() ); n.addMixin( FACET_NODE_TYPE ); for ( Map.Entry<String, String> entry : facet.toProperties().entrySet() ) { n.setProperty( entry.getKey(), entry.getValue() ); } } } catch ( RepositoryException e ) { throw new MetadataRepositoryException( e.getMessage(), e ); } } @Override public void updateNamespace( String repositoryId, String namespace ) throws MetadataRepositoryException { try { Node node = getOrAddNamespaceNode( repositoryId, namespace ); node.setProperty( "namespace", namespace ); } catch ( RepositoryException e ) { throw new MetadataRepositoryException( e.getMessage(), e ); } } @Override public void removeProject( String repositoryId, String namespace, String projectId ) throws MetadataRepositoryException { try { Node root = getJcrSession().getRootNode(); String namespacePath = getNamespacePath( repositoryId, namespace ); if ( root.hasNode( namespacePath ) ) { Iterator<Node> nodeIterator = JcrUtils.getChildNodes( root.getNode( namespacePath ) ).iterator(); while ( nodeIterator.hasNext() ) { Node node = nodeIterator.next(); if ( node.isNodeType( PROJECT_NODE_TYPE ) && projectId.equals( node.getName() ) ) { node.remove(); } } } } catch ( RepositoryException e ) { throw new MetadataRepositoryException( e.getMessage(), e ); } } @Override public boolean hasMetadataFacet( String repositoryId, String facetId ) throws MetadataRepositoryException { try { Node node = getJcrSession().getRootNode().getNode( getFacetPath( repositoryId, facetId ) ); return node.getNodes().hasNext(); } catch ( PathNotFoundException e ) { // ignored - the facet doesn't exist, so return false return false; } catch ( RepositoryException e ) { throw new MetadataRepositoryException( e.getMessage(), e ); } } @Override public List<String> getMetadataFacets( String repositoryId, String facetId ) throws MetadataRepositoryException { List<String> facets = new ArrayList<>(); try { // no need to construct node-by-node here, as we'll find in the next instance, the facet names have / and // are paths themselves Node node = getJcrSession().getRootNode().getNode( getFacetPath( repositoryId, facetId ) ); // TODO: this is a bit awkward. Might be better to review the purpose of this function - why is the list of // paths helpful? recurse( facets, "", node ); } catch ( PathNotFoundException e ) { // ignored - the facet doesn't exist, so return the empty list } catch ( RepositoryException e ) { throw new MetadataRepositoryException( e.getMessage(), e ); } return facets; } private void recurse( List<String> facets, String prefix, Node node ) throws RepositoryException { for ( Node n : JcrUtils.getChildNodes( node ) ) { String name = prefix + "/" + n.getName(); if ( n.hasNodes() ) { recurse( facets, name, n ); } else { // strip leading / first facets.add( name.substring( 1 ) ); } } } @Override public MetadataFacet getMetadataFacet( String repositoryId, String facetId, String name ) throws MetadataRepositoryException { MetadataFacet metadataFacet = null; try { Node root = getJcrSession().getRootNode(); Node node = root.getNode( getFacetPath( repositoryId, facetId, name ) ); if ( metadataFacetFactories == null ) { return metadataFacet; } MetadataFacetFactory metadataFacetFactory = metadataFacetFactories.get( facetId ); if ( metadataFacetFactory != null ) { metadataFacet = metadataFacetFactory.createMetadataFacet( repositoryId, name ); Map<String, String> map = new HashMap<>(); for ( Property property : JcrUtils.getProperties( node ) ) { String p = property.getName(); if ( !p.startsWith( "jcr:" ) ) { map.put( p, property.getString() ); } } metadataFacet.fromProperties( map ); } } catch ( PathNotFoundException e ) { // ignored - the facet doesn't exist, so return null } catch ( RepositoryException e ) { throw new MetadataRepositoryException( e.getMessage(), e ); } return metadataFacet; } @Override public void addMetadataFacet( String repositoryId, MetadataFacet metadataFacet ) throws MetadataRepositoryException { try { Node repo = getOrAddRepositoryNode( repositoryId ); Node facets = JcrUtils.getOrAddNode( repo, "facets" ); String id = metadataFacet.getFacetId(); Node facetNode = JcrUtils.getOrAddNode( facets, id ); Node node = getOrAddNodeByPath( facetNode, metadataFacet.getName() ); for ( Map.Entry<String, String> entry : metadataFacet.toProperties().entrySet() ) { node.setProperty( entry.getKey(), entry.getValue() ); } } catch ( RepositoryException e ) { throw new MetadataRepositoryException( e.getMessage(), e ); } } @Override public void removeNamespace( String repositoryId, String projectId ) throws MetadataRepositoryException { try { Node root = getJcrSession().getRootNode(); String path = getNamespacePath( repositoryId, projectId ); if ( root.hasNode( path ) ) { Node node = root.getNode( path ); if ( node.isNodeType( NAMESPACE_NODE_TYPE ) ) { node.remove(); } } } catch ( RepositoryException e ) { throw new MetadataRepositoryException( e.getMessage(), e ); } } @Override public void removeMetadataFacets( String repositoryId, String facetId ) throws MetadataRepositoryException { try { Node root = getJcrSession().getRootNode(); String path = getFacetPath( repositoryId, facetId ); if ( root.hasNode( path ) ) { root.getNode( path ).remove(); } } catch ( RepositoryException e ) { throw new MetadataRepositoryException( e.getMessage(), e ); } } @Override public void removeMetadataFacet( String repositoryId, String facetId, String name ) throws MetadataRepositoryException { try { Node root = getJcrSession().getRootNode(); String path = getFacetPath( repositoryId, facetId, name ); if ( root.hasNode( path ) ) { Node node = root.getNode( path ); do { // also remove empty container nodes Node parent = node.getParent(); node.remove(); node = parent; } while ( !node.hasNodes() ); } } catch ( RepositoryException e ) { throw new MetadataRepositoryException( e.getMessage(), e ); } } @Override public List<ArtifactMetadata> getArtifactsByDateRange( String repoId, Date startTime, Date endTime ) throws MetadataRepositoryException { List<ArtifactMetadata> artifacts; String q = getArtifactQuery( repoId ); if ( startTime != null ) { q += " AND [whenGathered] >= $start"; } if ( endTime != null ) { q += " AND [whenGathered] <= $end"; } try { Query query = getJcrSession().getWorkspace().getQueryManager().createQuery( q, Query.JCR_SQL2 ); ValueFactory valueFactory = getJcrSession().getValueFactory(); if ( startTime != null ) { query.bindValue( "start", valueFactory.createValue( createCalendar( startTime ) ) ); } if ( endTime != null ) { query.bindValue( "end", valueFactory.createValue( createCalendar( endTime ) ) ); } QueryResult result = query.execute(); artifacts = new ArrayList<>(); for ( Node n : JcrUtils.getNodes( result ) ) { artifacts.add( getArtifactFromNode( repoId, n ) ); } } catch ( RepositoryException e ) { throw new MetadataRepositoryException( e.getMessage(), e ); } return artifacts; } @Override public Collection<String> getRepositories() throws MetadataRepositoryException { List<String> repositories; try { Node root = getJcrSession().getRootNode(); if ( root.hasNode( "repositories" ) ) { Node node = root.getNode( "repositories" ); repositories = new ArrayList<>(); NodeIterator i = node.getNodes(); while ( i.hasNext() ) { Node n = i.nextNode(); repositories.add( n.getName() ); } } else { repositories = Collections.emptyList(); } } catch ( RepositoryException e ) { throw new MetadataRepositoryException( e.getMessage(), e ); } return repositories; } @Override public List<ArtifactMetadata> getArtifactsByChecksum( String repositoryId, String checksum ) throws MetadataRepositoryException { List<ArtifactMetadata> artifacts; String q = getArtifactQuery( repositoryId ) + " AND ([sha1] = $checksum OR [md5] = $checksum)"; try { Query query = getJcrSession().getWorkspace().getQueryManager().createQuery( q, Query.JCR_SQL2 ); ValueFactory valueFactory = getJcrSession().getValueFactory(); query.bindValue( "checksum", valueFactory.createValue( checksum ) ); QueryResult result = query.execute(); artifacts = new ArrayList<>(); for ( Node n : JcrUtils.getNodes( result ) ) { artifacts.add( getArtifactFromNode( repositoryId, n ) ); } } catch ( RepositoryException e ) { throw new MetadataRepositoryException( e.getMessage(), e ); } return artifacts; } private List<ArtifactMetadata> runJcrQuery( String repositoryId, String q, Map<String, String> bindings ) throws MetadataRepositoryException { List<ArtifactMetadata> artifacts; if ( repositoryId != null ) { q += " AND ISDESCENDANTNODE(artifact,'/" + getRepositoryContentPath( repositoryId ) + "')"; } log.info( "Running JCR Query: {}", q ); try { Query query = getJcrSession().getWorkspace().getQueryManager().createQuery( q, Query.JCR_SQL2 ); ValueFactory valueFactory = getJcrSession().getValueFactory(); for ( Entry<String, String> entry : bindings.entrySet() ) { query.bindValue( entry.getKey(), valueFactory.createValue( entry.getValue() ) ); } long start = Calendar.getInstance().getTimeInMillis(); QueryResult result = query.execute(); long end = Calendar.getInstance().getTimeInMillis(); log.info( "JCR Query ran in {} milliseconds: {}", end - start , q ); artifacts = new ArrayList<>(); RowIterator rows = result.getRows(); while ( rows.hasNext() ) { Row row = rows.nextRow(); Node node = row.getNode( "artifact" ); artifacts.add( getArtifactFromNode( repositoryId, node ) ); } } catch ( RepositoryException e ) { throw new MetadataRepositoryException( e.getMessage(), e ); } return artifacts; } @Override public List<ArtifactMetadata> getArtifactsByProjectVersionMetadata( String key, String value, String repositoryId ) throws MetadataRepositoryException { String q = "SELECT * FROM [" + PROJECT_VERSION_NODE_TYPE + "] AS projectVersion INNER JOIN [" + ARTIFACT_NODE_TYPE + "] AS artifact ON ISCHILDNODE(artifact, projectVersion) INNER JOIN [" + FACET_NODE_TYPE + "] AS facet ON ISCHILDNODE(facet, projectVersion) WHERE ([facet].[" + key + "] = $value)"; return runJcrQuery( repositoryId, q, ImmutableMap.of( "value", value ) ); } @Override public List<ArtifactMetadata> getArtifactsByMetadata( String key, String value, String repositoryId ) throws MetadataRepositoryException { String q = "SELECT * FROM [" + ARTIFACT_NODE_TYPE + "] AS artifact INNER JOIN [" + FACET_NODE_TYPE + "] AS facet ON ISCHILDNODE(facet, artifact) WHERE ([facet].[" + key + "] = $value)"; return runJcrQuery( repositoryId, q, ImmutableMap.of( "value", value ) ); } @Override public List<ArtifactMetadata> getArtifactsByProperty( String key, String value, String repositoryId ) throws MetadataRepositoryException { String q = "SELECT * FROM [" + PROJECT_VERSION_NODE_TYPE + "] AS projectVersion INNER JOIN [" + ARTIFACT_NODE_TYPE + "] AS artifact ON ISCHILDNODE(artifact, projectVersion) WHERE ([projectVersion].[" + key + "] = $value)"; return runJcrQuery( repositoryId, q, ImmutableMap.of( "value", value ) ); } @Override public void removeRepository( String repositoryId ) throws MetadataRepositoryException { try { Node root = getJcrSession().getRootNode(); String path = getRepositoryPath( repositoryId ); if ( root.hasNode( path ) ) { root.getNode( path ).remove(); } } catch ( RepositoryException e ) { throw new MetadataRepositoryException( e.getMessage(), e ); } } @Override public List<ArtifactMetadata> getArtifacts( String repositoryId ) throws MetadataRepositoryException { List<ArtifactMetadata> artifacts; String q = getArtifactQuery( repositoryId ); try { Query query = getJcrSession().getWorkspace().getQueryManager().createQuery( q, Query.JCR_SQL2 ); QueryResult result = query.execute(); artifacts = new ArrayList<>(); for ( Node n : JcrUtils.getNodes( result ) ) { if ( n.isNodeType( ARTIFACT_NODE_TYPE ) ) { artifacts.add( getArtifactFromNode( repositoryId, n ) ); } } } catch ( RepositoryException e ) { throw new MetadataRepositoryException( e.getMessage(), e ); } return artifacts; } private static String getArtifactQuery( String repositoryId ) { return "SELECT * FROM [" + ARTIFACT_NODE_TYPE + "] AS artifact WHERE ISDESCENDANTNODE(artifact,'/" + getRepositoryContentPath( repositoryId ) + "')"; } @Override public ProjectMetadata getProject( String repositoryId, String namespace, String projectId ) throws MetadataResolutionException { ProjectMetadata metadata = null; try { Node root = getJcrSession().getRootNode(); // basically just checking it exists String path = getProjectPath( repositoryId, namespace, projectId ); if ( root.hasNode( path ) ) { metadata = new ProjectMetadata(); metadata.setId( projectId ); metadata.setNamespace( namespace ); } } catch ( RepositoryException e ) { throw new MetadataResolutionException( e.getMessage(), e ); } return metadata; } @Override public ProjectVersionMetadata getProjectVersion( String repositoryId, String namespace, String projectId, String projectVersion ) throws MetadataResolutionException { ProjectVersionMetadata versionMetadata; try { Node root = getJcrSession().getRootNode(); String path = getProjectVersionPath( repositoryId, namespace, projectId, projectVersion ); if ( !root.hasNode( path ) ) { return null; } Node node = root.getNode( path ); versionMetadata = new ProjectVersionMetadata(); versionMetadata.setId( projectVersion ); versionMetadata.setName( getPropertyString( node, "name" ) ); versionMetadata.setDescription( getPropertyString( node, "description" ) ); versionMetadata.setUrl( getPropertyString( node, "url" ) ); versionMetadata.setIncomplete( node.hasProperty( "incomplete" ) && node.getProperty( "incomplete" ).getBoolean() ); // FIXME: decide how to treat these in the content repo String scmConnection = getPropertyString( node, "scm.connection" ); String scmDeveloperConnection = getPropertyString( node, "scm.developerConnection" ); String scmUrl = getPropertyString( node, "scm.url" ); if ( scmConnection != null || scmDeveloperConnection != null || scmUrl != null ) { Scm scm = new Scm(); scm.setConnection( scmConnection ); scm.setDeveloperConnection( scmDeveloperConnection ); scm.setUrl( scmUrl ); versionMetadata.setScm( scm ); } String ciSystem = getPropertyString( node, "ci.system" ); String ciUrl = getPropertyString( node, "ci.url" ); if ( ciSystem != null || ciUrl != null ) { CiManagement ci = new CiManagement(); ci.setSystem( ciSystem ); ci.setUrl( ciUrl ); versionMetadata.setCiManagement( ci ); } String issueSystem = getPropertyString( node, "issue.system" ); String issueUrl = getPropertyString( node, "issue.url" ); if ( issueSystem != null || issueUrl != null ) { IssueManagement issueManagement = new IssueManagement(); issueManagement.setSystem( issueSystem ); issueManagement.setUrl( issueUrl ); versionMetadata.setIssueManagement( issueManagement ); } String orgName = getPropertyString( node, "org.name" ); String orgUrl = getPropertyString( node, "org.url" ); if ( orgName != null || orgUrl != null ) { Organization org = new Organization(); org.setName( orgName ); org.setUrl( orgUrl ); versionMetadata.setOrganization( org ); } boolean done = false; int i = 0; while ( !done ) { String licenseName = getPropertyString( node, "license." + i + ".name" ); String licenseUrl = getPropertyString( node, "license." + i + ".url" ); if ( licenseName != null || licenseUrl != null ) { License license = new License(); license.setName( licenseName ); license.setUrl( licenseUrl ); versionMetadata.addLicense( license ); } else { done = true; } i++; } done = false; i = 0; while ( !done ) { String mailingListName = getPropertyString( node, "mailingList." + i + ".name" ); if ( mailingListName != null ) { MailingList mailingList = new MailingList(); mailingList.setName( mailingListName ); mailingList.setMainArchiveUrl( getPropertyString( node, "mailingList." + i + ".archive" ) ); String n = "mailingList." + i + ".otherArchives"; if ( node.hasProperty( n ) ) { mailingList.setOtherArchives( Arrays.asList( getPropertyString( node, n ).split( "," ) ) ); } else { mailingList.setOtherArchives( Collections.<String>emptyList() ); } mailingList.setPostAddress( getPropertyString( node, "mailingList." + i + ".post" ) ); mailingList.setSubscribeAddress( getPropertyString( node, "mailingList." + i + ".subscribe" ) ); mailingList.setUnsubscribeAddress( getPropertyString( node, "mailingList." + i + ".unsubscribe" ) ); versionMetadata.addMailingList( mailingList ); } else { done = true; } i++; } if ( node.hasNode( "dependencies" ) ) { Node dependenciesNode = node.getNode( "dependencies" ); for ( Node n : JcrUtils.getChildNodes( dependenciesNode ) ) { if ( n.isNodeType( DEPENDENCY_NODE_TYPE ) ) { Dependency dependency = new Dependency(); // FIXME: correct these properties dependency.setArtifactId( getPropertyString( n, "artifactId" ) ); dependency.setGroupId( getPropertyString( n, "groupId" ) ); dependency.setClassifier( getPropertyString( n, "classifier" ) ); dependency.setOptional( Boolean.valueOf( getPropertyString( n, "optional" ) ) ); dependency.setScope( getPropertyString( n, "scope" ) ); dependency.setSystemPath( getPropertyString( n, "systemPath" ) ); dependency.setType( getPropertyString( n, "type" ) ); dependency.setVersion( getPropertyString( n, "version" ) ); versionMetadata.addDependency( dependency ); } } } for ( Node n : JcrUtils.getChildNodes( node ) ) { if ( n.isNodeType( FACET_NODE_TYPE ) ) { String name = n.getName(); MetadataFacetFactory factory = metadataFacetFactories.get( name ); if ( factory == null ) { log.error( "Attempted to load unknown project version metadata facet: {}", name ); } else { MetadataFacet facet = factory.createMetadataFacet(); Map<String, String> map = new HashMap<>(); for ( Property property : JcrUtils.getProperties( n ) ) { String p = property.getName(); if ( !p.startsWith( "jcr:" ) ) { map.put( p, property.getString() ); } } facet.fromProperties( map ); versionMetadata.addFacet( facet ); } } } } catch ( RepositoryException e ) { throw new MetadataResolutionException( e.getMessage(), e ); } return versionMetadata; } @Override public Collection<String> getArtifactVersions( String repositoryId, String namespace, String projectId, String projectVersion ) throws MetadataResolutionException { Set<String> versions = new LinkedHashSet<String>(); try { Node root = getJcrSession().getRootNode(); Node node = root.getNode( getProjectVersionPath( repositoryId, namespace, projectId, projectVersion ) ); for ( Node n : JcrUtils.getChildNodes( node ) ) { versions.add( n.getProperty( "version" ).getString() ); } } catch ( PathNotFoundException e ) { // ignore repo not found for now } catch ( RepositoryException e ) { throw new MetadataResolutionException( e.getMessage(), e ); } return versions; } @Override public Collection<ProjectVersionReference> getProjectReferences( String repositoryId, String namespace, String projectId, String projectVersion ) throws MetadataResolutionException { List<ProjectVersionReference> references = new ArrayList<>(); // TODO: bind variables instead String q = "SELECT * FROM [archiva:dependency] WHERE ISDESCENDANTNODE([/repositories/" + repositoryId + "/content]) AND [groupId]='" + namespace + "' AND [artifactId]='" + projectId + "'"; if ( projectVersion != null ) { q += " AND [version]='" + projectVersion + "'"; } try { Query query = getJcrSession().getWorkspace().getQueryManager().createQuery( q, Query.JCR_SQL2 ); QueryResult result = query.execute(); for ( Node n : JcrUtils.getNodes( result ) ) { n = n.getParent(); // dependencies grouping element n = n.getParent(); // project version String usedByProjectVersion = n.getName(); n = n.getParent(); // project String usedByProject = n.getName(); n = n.getParent(); // namespace String usedByNamespace = n.getProperty( "namespace" ).getString(); ProjectVersionReference ref = new ProjectVersionReference(); ref.setNamespace( usedByNamespace ); ref.setProjectId( usedByProject ); ref.setProjectVersion( usedByProjectVersion ); ref.setReferenceType( ProjectVersionReference.ReferenceType.DEPENDENCY ); references.add( ref ); } } catch ( RepositoryException e ) { throw new MetadataResolutionException( e.getMessage(), e ); } return references; } @Override public Collection<String> getRootNamespaces( String repositoryId ) throws MetadataResolutionException { return getNamespaces( repositoryId, null ); } @Override public Collection<String> getNamespaces( String repositoryId, String baseNamespace ) throws MetadataResolutionException { String path = baseNamespace != null ? getNamespacePath( repositoryId, baseNamespace ) : getRepositoryContentPath( repositoryId ); return getNodeNames( path, NAMESPACE_NODE_TYPE ); } @Override public Collection<String> getProjects( String repositoryId, String namespace ) throws MetadataResolutionException { return getNodeNames( getNamespacePath( repositoryId, namespace ), PROJECT_NODE_TYPE ); } @Override public Collection<String> getProjectVersions( String repositoryId, String namespace, String projectId ) throws MetadataResolutionException { return getNodeNames( getProjectPath( repositoryId, namespace, projectId ), PROJECT_VERSION_NODE_TYPE ); } @Override public void removeArtifact( ArtifactMetadata artifactMetadata, String baseVersion ) throws MetadataRepositoryException { String repositoryId = artifactMetadata.getRepositoryId(); try { Node root = getJcrSession().getRootNode(); String path = getProjectVersionPath( repositoryId, artifactMetadata.getNamespace(), artifactMetadata.getProject(), baseVersion ); if ( root.hasNode( path ) ) { Node node = root.getNode( path ); for ( Node n : JcrUtils.getChildNodes( node ) ) { if ( n.isNodeType( ARTIFACT_NODE_TYPE ) ) { if ( n.hasProperty( "version" ) ) { String version = n.getProperty( "version" ).getString(); if ( StringUtils.equals( version, artifactMetadata.getVersion() ) ) { n.remove(); } } } } } } catch ( RepositoryException e ) { throw new MetadataRepositoryException( e.getMessage(), e ); } } @Override public void removeProjectVersion( String repoId, String namespace, String projectId, String projectVersion ) throws MetadataRepositoryException { try { String path = getProjectPath( repoId, namespace, projectId ); Node root = getJcrSession().getRootNode(); Node nodeAtPath = root.getNode( path ); for ( Node node : JcrUtils.getChildNodes( nodeAtPath ) ) { if ( node.isNodeType( PROJECT_VERSION_NODE_TYPE ) && StringUtils.equals( projectVersion, node.getName() ) ) { node.remove(); } } } catch ( RepositoryException e ) { throw new MetadataRepositoryException( e.getMessage(), e ); } } @Override public void removeArtifact( String repositoryId, String namespace, String projectId, String projectVersion, String id ) throws MetadataRepositoryException { try { Node root = getJcrSession().getRootNode(); String path = getArtifactPath( repositoryId, namespace, projectId, projectVersion, id ); if ( root.hasNode( path ) ) { root.getNode( path ).remove(); } // remove version path = getProjectPath( repositoryId, namespace, projectId ); Node nodeAtPath = root.getNode( path ); for ( Node node : JcrUtils.getChildNodes( nodeAtPath ) ) { if ( node.isNodeType( PROJECT_VERSION_NODE_TYPE ) // && StringUtils.equals( node.getName(), projectVersion ) ) { node.remove(); } } } catch ( RepositoryException e ) { throw new MetadataRepositoryException( e.getMessage(), e ); } } @Override public void removeArtifact( String repositoryId, String namespace, String project, String projectVersion, MetadataFacet metadataFacet ) throws MetadataRepositoryException { try { Node root = getJcrSession().getRootNode(); String path = getProjectVersionPath( repositoryId, namespace, project, projectVersion ); if ( root.hasNode( path ) ) { Node node = root.getNode( path ); for ( Node n : JcrUtils.getChildNodes( node ) ) { if ( n.isNodeType( ARTIFACT_NODE_TYPE ) ) { ArtifactMetadata artifactMetadata = getArtifactFromNode( repositoryId, n ); log.debug( "artifactMetadata: {}", artifactMetadata ); MetadataFacet metadataFacetToRemove = artifactMetadata.getFacet( metadataFacet.getFacetId() ); if ( metadataFacetToRemove != null && metadataFacet.equals( metadataFacetToRemove ) ) { n.remove(); } } } } } catch ( RepositoryException e ) { throw new MetadataRepositoryException( e.getMessage(), e ); } } @Override public Collection<ArtifactMetadata> getArtifacts( String repositoryId, String namespace, String projectId, String projectVersion ) throws MetadataResolutionException { List<ArtifactMetadata> artifacts = new ArrayList<>(); try { Node root = getJcrSession().getRootNode(); String path = getProjectVersionPath( repositoryId, namespace, projectId, projectVersion ); if ( root.hasNode( path ) ) { Node node = root.getNode( path ); for ( Node n : JcrUtils.getChildNodes( node ) ) { if ( n.isNodeType( ARTIFACT_NODE_TYPE ) ) { artifacts.add( getArtifactFromNode( repositoryId, n ) ); } } } } catch ( RepositoryException e ) { throw new MetadataResolutionException( e.getMessage(), e ); } return artifacts; } @Override public void save() { try { getJcrSession().save(); } catch ( InvalidItemStateException e ) { // olamy this might happen when deleting a repo while is under scanning log.warn( "skip InvalidItemStateException:" + e.getMessage(), e ); } catch ( RepositoryException e ) { throw new RuntimeException( e.getMessage(), e ); } } @Override public void revert() { try { getJcrSession().refresh( false ); } catch ( RepositoryException e ) { throw new RuntimeException( e.getMessage(), e ); } } @Override public boolean canObtainAccess( Class<?> aClass ) { return aClass == Session.class; } @Override public <T>T obtainAccess( Class<T> aClass ) throws MetadataRepositoryException { if ( aClass == Session.class ) { try { return (T) getJcrSession(); } catch ( RepositoryException e ) { log.error( e.getMessage(), e ); throw new MetadataRepositoryException( e.getMessage(), e ); } } throw new IllegalArgumentException( "Access using " + aClass + " is not supported on the JCR metadata storage" ); } @Override public void close() throws MetadataRepositoryException { if ( jcrSession != null && jcrSession.isLive() ) { jcrSession.logout(); } } /** * Exact is ignored as we can't do exact search in any property, we need a key */ @Override public List<ArtifactMetadata> searchArtifacts( String text, String repositoryId, boolean exact ) throws MetadataRepositoryException { return searchArtifacts( null, text, repositoryId, exact ); } @Override public List<ArtifactMetadata> searchArtifacts( String key, String text, String repositoryId, boolean exact ) throws MetadataRepositoryException { // we can't do exact search in any property (*), we need a key boolean e = exact && key != null; String theKey = key == null ? "*" : "[" + key + "]"; String projectVersionCondition = e ? "(projectVersion." + theKey + " = $value)" : "contains([projectVersion]." + theKey + ", $value)"; String facetCondition = e ? "(facet." + theKey + " = $value)" : "contains([facet]." + theKey + ", $value)"; String q = "SELECT * FROM [" + PROJECT_VERSION_NODE_TYPE + "] AS projectVersion LEFT OUTER JOIN [" + ARTIFACT_NODE_TYPE + "] AS artifact ON ISCHILDNODE(artifact, projectVersion) LEFT OUTER JOIN [" + FACET_NODE_TYPE + "] AS facet ON ISCHILDNODE(facet, projectVersion) WHERE (" + projectVersionCondition + " OR " + facetCondition + ")"; return runJcrQuery( repositoryId, q, ImmutableMap.of( "value", text ) ); } private ArtifactMetadata getArtifactFromNode( String repositoryId, Node artifactNode ) throws RepositoryException { String id = artifactNode.getName(); ArtifactMetadata artifact = new ArtifactMetadata(); artifact.setId( id ); artifact.setRepositoryId( repositoryId == null ? artifactNode.getAncestor(2).getName() : repositoryId ); Node projectVersionNode = artifactNode.getParent(); Node projectNode = projectVersionNode.getParent(); Node namespaceNode = projectNode.getParent(); artifact.setNamespace( namespaceNode.getProperty( "namespace" ).getString() ); artifact.setProject( projectNode.getName() ); artifact.setProjectVersion( projectVersionNode.getName() ); artifact.setVersion( artifactNode.hasProperty( "version" ) ? artifactNode.getProperty( "version" ).getString() : projectVersionNode.getName() ); if ( artifactNode.hasProperty( JCR_LAST_MODIFIED ) ) { artifact.setFileLastModified( artifactNode.getProperty( JCR_LAST_MODIFIED ).getDate().getTimeInMillis() ); } if ( artifactNode.hasProperty( "whenGathered" ) ) { artifact.setWhenGathered( artifactNode.getProperty( "whenGathered" ).getDate().getTime() ); } if ( artifactNode.hasProperty( "size" ) ) { artifact.setSize( artifactNode.getProperty( "size" ).getLong() ); } if ( artifactNode.hasProperty( "md5" ) ) { artifact.setMd5( artifactNode.getProperty( "md5" ).getString() ); } if ( artifactNode.hasProperty( "sha1" ) ) { artifact.setSha1( artifactNode.getProperty( "sha1" ).getString() ); } for ( Node n : JcrUtils.getChildNodes( artifactNode ) ) { if ( n.isNodeType( FACET_NODE_TYPE ) ) { String name = n.getName(); MetadataFacetFactory factory = metadataFacetFactories.get( name ); if ( factory == null ) { log.error( "Attempted to load unknown project version metadata facet: " + name ); } else { MetadataFacet facet = factory.createMetadataFacet(); Map<String, String> map = new HashMap<>(); for ( Property p : JcrUtils.getProperties( n ) ) { String property = p.getName(); if ( !property.startsWith( "jcr:" ) ) { map.put( property, p.getString() ); } } facet.fromProperties( map ); artifact.addFacet( facet ); } } } return artifact; } private static String getPropertyString( Node node, String name ) throws RepositoryException { return node.hasProperty( name ) ? node.getProperty( name ).getString() : null; } private Collection<String> getNodeNames( String path, String nodeType ) throws MetadataResolutionException { List<String> names = new ArrayList<>(); try { Node root = getJcrSession().getRootNode(); Node nodeAtPath = root.getNode( path ); for ( Node node : JcrUtils.getChildNodes( nodeAtPath ) ) { if ( node.isNodeType( nodeType ) ) { names.add( node.getName() ); } } } catch ( PathNotFoundException e ) { // ignore repo not found for now } catch ( RepositoryException e ) { throw new MetadataResolutionException( e.getMessage(), e ); } return names; } private static String getRepositoryPath( String repositoryId ) { return "repositories/" + repositoryId; } private static String getRepositoryContentPath( String repositoryId ) { return getRepositoryPath( repositoryId ) + "/content/"; } private static String getFacetPath( String repositoryId, String facetId ) { return getRepositoryPath( repositoryId ) + "/facets/" + facetId; } private static String getNamespacePath( String repositoryId, String namespace ) { return getRepositoryContentPath( repositoryId ) + namespace.replace( '.', '/' ); } private static String getProjectPath( String repositoryId, String namespace, String projectId ) { return getNamespacePath( repositoryId, namespace ) + "/" + projectId; } private static String getProjectVersionPath( String repositoryId, String namespace, String projectId, String projectVersion ) { return getProjectPath( repositoryId, namespace, projectId ) + "/" + projectVersion; } private static String getArtifactPath( String repositoryId, String namespace, String projectId, String projectVersion, String id ) { return getProjectVersionPath( repositoryId, namespace, projectId, projectVersion ) + "/" + id; } private Node getOrAddNodeByPath( Node baseNode, String name ) throws RepositoryException { return getOrAddNodeByPath( baseNode, name, null ); } private Node getOrAddNodeByPath( Node baseNode, String name, String nodeType ) throws RepositoryException { Node node = baseNode; for ( String n : name.split( "/" ) ) { node = JcrUtils.getOrAddNode( node, n ); if ( nodeType != null ) { node.addMixin( nodeType ); } } return node; } private static String getFacetPath( String repositoryId, String facetId, String name ) { return getFacetPath( repositoryId, facetId ) + "/" + name; } private Node getOrAddRepositoryNode( String repositoryId ) throws RepositoryException { Node root = getJcrSession().getRootNode(); Node node = JcrUtils.getOrAddNode( root, "repositories" ); node = JcrUtils.getOrAddNode( node, repositoryId ); return node; } private Node getOrAddRepositoryContentNode( String repositoryId ) throws RepositoryException { Node node = getOrAddRepositoryNode( repositoryId ); return JcrUtils.getOrAddNode( node, "content" ); } private Node getOrAddNamespaceNode( String repositoryId, String namespace ) throws RepositoryException { Node repo = getOrAddRepositoryContentNode( repositoryId ); return getOrAddNodeByPath( repo, namespace.replace( '.', '/' ), NAMESPACE_NODE_TYPE ); } private Node getOrAddProjectNode( String repositoryId, String namespace, String projectId ) throws RepositoryException { Node namespaceNode = getOrAddNamespaceNode( repositoryId, namespace ); Node node = JcrUtils.getOrAddNode( namespaceNode, projectId ); node.addMixin( PROJECT_NODE_TYPE ); return node; } private Node getOrAddProjectVersionNode( String repositoryId, String namespace, String projectId, String projectVersion ) throws RepositoryException { Node projectNode = getOrAddProjectNode( repositoryId, namespace, projectId ); Node node = JcrUtils.getOrAddNode( projectNode, projectVersion ); node.addMixin( PROJECT_VERSION_NODE_TYPE ); return node; } private Node getOrAddArtifactNode( String repositoryId, String namespace, String projectId, String projectVersion, String id ) throws RepositoryException { Node versionNode = getOrAddProjectVersionNode( repositoryId, namespace, projectId, projectVersion ); Node node = JcrUtils.getOrAddNode( versionNode, id ); node.addMixin( ARTIFACT_NODE_TYPE ); return node; } private static Calendar createCalendar( Date time ) { Calendar cal = Calendar.getInstance(); cal.setTime( time ); return cal; } private String join( Collection<String> ids ) { if ( ids != null && !ids.isEmpty() ) { StringBuilder s = new StringBuilder(); for ( String id : ids ) { s.append( id ); s.append( "," ); } return s.substring( 0, s.length() - 1 ); } return null; } public Session getJcrSession() throws RepositoryException { if ( this.jcrSession == null || !this.jcrSession.isLive() ) { jcrSession = repository.login( new SimpleCredentials( "admin", "admin".toCharArray() ) ); } return this.jcrSession; } }